package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.json.JSONUtil;
import com.aliyun.iot20180120.Client;
import com.aliyun.iot20180120.models.*;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.constant.Constants;
import com.zzyl.dto.DeviceDataPageReqDto;
import com.zzyl.dto.DeviceDto;
import com.zzyl.dto.DevicePageQueryDto;
import com.zzyl.entity.Content;
import com.zzyl.entity.Device;
import com.zzyl.entity.DeviceData;
import com.zzyl.enums.BasicEnum;
import com.zzyl.exception.BaseException;
import com.zzyl.mapper.DeviceDataMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.properties.AliIoTConfigProperties;
import com.zzyl.service.IotService;
import com.zzyl.utils.ObjectUtil;
import com.zzyl.vo.DeviceDataGraphVo;
import com.zzyl.vo.DeviceDataVo;
import com.zzyl.vo.DeviceVo;
import com.zzyl.vo.ProductVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
public class IotServiceImpl implements IotService {

    /**
     * 自动注入Client对象，用于与外部服务进行通信。
     */
    @Autowired
    private Client client;

    /**
     * 自动注入AliIoTConfigProperties对象，用于获取物联网平台的配置属性。
     */
    @Autowired
    private AliIoTConfigProperties iotProp;

    /**
     * 自动注入StringRedisTemplate对象，用于操作Redis中的字符串数据。
     */
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 自动注入DeviceMapper对象，用于数据库中设备信息的CRUD操作。
     */
    @Autowired
    private DeviceMapper deviceMapper;

    @Override
    public void syncProductList() {
        int page = 1;
        while (true) {
            // 调用sdk方法,请求iot平台,获取数据
            // 创建查询产品列表的请求对象
            QueryProductListRequest request = new QueryProductListRequest();

            // 设置当前页码和每页显示数量
            request.setCurrentPage(page);
            request.setPageSize(1);

            // 设置实例ID，用于区分不同的物联网平台实例
            request.setIotInstanceId(iotProp.getIotInstanceId());

            // 执行查询操作，尝试获取产品列表
            QueryProductListResponse response = null;
            try {
                response = client.queryProductList(request);
            } catch (Exception e) {
                // 日志记录查询过程中出现的异常
                e.printStackTrace();
            }

            // 检查查询结果是否为空，为空则表示查询失败
            if (response == null) {
                // 日志记录获取数据失败的信息
                log.error("获取数据失败");
                // 抛出自定义异常，表示查询产品列表失败
                throw new BaseException(BasicEnum.IOT_LIST_PRODUCT_ERROR);
            }

            // 检查查询操作是否成功
            Boolean b = response.getBody().getSuccess();
            if (!b) {
                // 日志记录获取数据失败的信息
                log.error("获取数据失败");
                // 抛出自定义异常，表示查询产品列表失败
                throw new BaseException(BasicEnum.IOT_LIST_PRODUCT_ERROR);
            }

            // 解析查询结果，获取产品信息列表
            QueryProductListResponseBody.QueryProductListResponseBodyData data = response.getBody().getData();
            QueryProductListResponseBody.QueryProductListResponseBodyDataList list = data.getList();
            List<QueryProductListResponseBody.QueryProductListResponseBodyDataListProductInfo> productInfoList = list.getProductInfo();

            // 将产品信息列表中的每个产品信息存储到Redis中
            for (QueryProductListResponseBody.QueryProductListResponseBodyDataListProductInfo info : productInfoList) {
                // 使用Redis的hash操作，将产品信息以键值对形式存储
                // "iot:product"作为hash的key，产品Key作为field，产品信息的JSON字符串作为value
                redisTemplate.opsForHash().put("iot:product", info.getProductKey(), JSONUtil.toJsonStr(info));
            }

            // 判断是否还有更多的页码需要查询
            if (data.getPageCount() > data.getCurrentPage()) {
                // 如果有更多页码，递增当前页码，继续查询下一页
                page++;
            } else {
                // 如果没有更多页码，结束查询循环
                break;
            }
        }
    }

    @Override
    public List<ProductVo> allProduct() {
//        没有访问redis,只是创建了一个对象
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps("iot:product");
        //        获取所有值
        List<String> values = hashOps.values();
        //        将值转换为对象
        List<ProductVo> voList = values
                .stream()
                .map(str -> JSONUtil.toBean(str, ProductVo.class))
                .collect(Collectors.toList());
        //        返回
        return voList;
    }

    /**
     * List<ProductVo> voList = new ArrayList<>();
     * for (String value : values) {
     * ProductVo1 productVo = JSONUtil.toBean(value, ProductVo. class);
     * voList.add(productVo);
     * }
     */
    @Override
    public void registerDevice(DeviceDto dto) {
//        查询设备名字是否存在
        int count = deviceMapper.selectByDeviceName(dto.getDeviceName());
        if (count > 0) {
            log.error("设备名已经存在");
            throw new BaseException(BasicEnum.DEVICE_NAME_EXIST);
        }
//        调用iot平台,创建设备
        RegisterDeviceRequest request = new RegisterDeviceRequest();
        // 设置实例ID，用于标识物联网平台的实例，确保请求针对正确的实例进行操作。
        request.setIotInstanceId(iotProp.getIotInstanceId());
        // 设置设备名称，作为设备的唯一标识符，用于在物联网平台上对设备进行管理和操作。
        request.setDeviceName(dto.getDeviceName());
        // 设置设备的昵称，提供一种更直观的设备标识，方便用户识别和管理设备。
        request.setNickname(dto.getNickname());
        // 设置产品Key，用于标识设备所属的产品，是物联网平台为产品生成的唯一标识。
        request.setProductKey(dto.getProductKey());

        RegisterDeviceResponse response = null;
        try {
            response = client.registerDevice(request);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (response == null || !response.getBody().getSuccess()) {
            log.error("调用iot注册设备报错");
            throw new BaseException(BasicEnum.IOT_REGISTER_DEVICE_ERROR);
        }
//        获取设备的id
        String iotId = response.getBody().getData().getIotId();
//        根据产品key,查询产品信息
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps("iot:product");
        String json = hashOps.get(dto.getProductKey());
//        if(StringUtils.isBlank(json)){
//            调用IOT平台查询 产品列表,如果查询回来的数据依旧是空的,就报错
//        }
        ProductVo productVo = JSONUtil.toBean(json, ProductVo.class);
//        把产品名称设置到设备对象中
//        把dto转实体类
        Device device = BeanUtil.toBean(dto, Device.class);
        device.setProductName(productVo.getProductName());
        device.setIotId(iotId);
//        不包含门禁
        device.setHaveEntranceGuard(0);
//        如果位置类型是0-随身设备,把设备的物理位置设置为-1(人)
        if (dto.getLocationType() == 0) {
            device.setPhysicalLocationType(-1);
        }
        try {
//        保存设备表数据
            int num = deviceMapper.insert(device);
            if (num != 1) {
                log.error("新增设备失败");
                throw new BaseException(BasicEnum.DEVICE_SAVE_ERROR);
            }
        } catch (Exception e) {
            e.printStackTrace();
//        如果保存失败,调用iot接口,删除iot的设备数据
            DeleteDeviceRequest request1 = new DeleteDeviceRequest();
            request1.setIotId(iotId);
            request1.setIotInstanceId(iotProp.getIotInstanceId());
            try {
                DeleteDeviceResponse response1 = client.deleteDevice(request1);
            } catch (Exception exception) {
                exception.printStackTrace();
                log.error("调用IOT删除设备失败");
            }
        }

    }

    @Override
    public PageResponse<DeviceVo> findByPage(DevicePageQueryDto dto) {

        PageHelper.startPage(dto.getPageNum(), dto.getPageSize());

        List<DeviceVo> voList = deviceMapper.selectByCondition(dto);
        Page page = (Page) voList;

        return PageResponse.of(page, DeviceVo.class);

    }

    @Override
    public DeviceVo findDeviceDetail(DeviceDto dto) {
//        根据产品和设备id 查询iot平台
        QueryDeviceDetailRequest request = new QueryDeviceDetailRequest();
        request.setIotInstanceId(iotProp.getIotInstanceId());
        request.setIotId(dto.getIotId());
        QueryDeviceDetailResponse response = null;
        try {
            response = client.queryDeviceDetail(request);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (response == null || !response.getBody().getSuccess()) {
            log.error("查询设备详情iot平台失败");
            throw new BaseException(BasicEnum.IOT_QUERY_DEVICE_ERROR);
        }
        QueryDeviceDetailResponseBody.QueryDeviceDetailResponseBodyData data = response.getBody().getData();
//        属性拷贝
        DeviceVo deviceVo = BeanUtil.toBean(data, DeviceVo.class);
//        根据设备id查询设备
        Device device = deviceMapper.selectByIotId(dto.getIotId());
//        合并数据
        BeanUtils.copyProperties(device, deviceVo);
        return deviceVo;
    }

    @Autowired
    private DeviceDataMapper deviceDataMapper;

    @Override
    public void batchSaveDeviceData(Content deviceData) {
        // 根据IoTID 查询设备
        Device device = deviceMapper.selectByIotId(deviceData.getIotId());
        // 如果设备不存在,任务结束
        if (device == null) {
            log.error("{}=设备不存在", deviceData.getIotId());
            return;
        }
        // 构造保存的数据结构, 执行保存的操作
        List<DeviceData> list = new ArrayList<>();
        // 从deviceData中获取所有items，这是一个映射关系，key代表特定的设备属性标识，val代表该属性的值
        Map<String, Content.Item> items = deviceData.getItems();

        // 遍历items映射，对每个属性项进行处理
        items.forEach((key, val) -> {
            // 根据当前设备的数据和当前属性的值，构建一个新的DeviceData实例
            // 这里使用了Builder模式来方便地设置DeviceData的属性
            DeviceData deviceData1 = DeviceData.builder()
                    .deviceName(device.getDeviceName()) // 设置设备名称
                    .iotId(device.getIotId()) // 设置设备的IoT ID
                    .nickname(device.getNickname()) // 设置设备的昵称
                    .productKey(device.getProductKey()) // 设置产品密钥
                    .productName(device.getProductName()) // 设置产品名称
                    .functionId(key) // 设置功能ID，这里使用了固定的值"key"
                    .accessLocation(device.getBindingLocation()) // 设置设备的访问位置
                    .locationType(device.getLocationType()) // 设置位置类型
                    .physicalLocationType(device.getPhysicalLocationType()) // 设置物理位置类型
                    .deviceDescription(device.getDeviceDescription()) // 设置设备描述
                    .dataValue(String.valueOf(val.getValue())) // 设置当前属性的值
                    .alarmTime(LocalDateTimeUtil.of(val.getTime()))
                    .build();

            // 将构建好的DeviceData实例添加到列表中
            list.add(deviceData1);
        });
        /**
         * 批量插入设备数据。
         *
         * 通过调用设备数据映射器的batchInsert方法，可以一次性将多个设备数据插入到数据库中。
         * 这种批量操作相比于单条记录插入，可以显著提高数据插入的效率，减少数据库访问的次数。
         *
         * @param list 待插入的设备数据列表。这个列表中的每个元素都应该是一个设备数据的实例，
         *             具体的实例类型应该与设备数据映射器所操作的数据对象类型相匹配。
         */
        deviceDataMapper.batchInsert(list);
        // 把最后一条数据保存在Redis
        // todo JSONUtil.toJsonStr(list)写错了
        redisTemplate.opsForHash().put(Constants.DEVICE_LASTDATA_CACHE_KEY, deviceData.getIotId(), JSONUtil.toJsonStr(list));
        /*deviceDataMapper.batchInsert(
    deviceData.getItems().values().stream()
        .map(val -> { // 修改了这里，因为map只需要一个参数，我们直接操作val
            return DeviceData.builder()
                .deviceName(device.getDeviceName()) // 确保device是正确的对象且在作用域内
                .iotId(device.getIotId())
                .nickname(device.getNickname())
                .productKey(device.getProductKey())
                .productName(device.getProductName())
                // 假设functionId应该是动态的，这里用val.getKey()代替了"key"
                .functionId(val.getKey())
                .accessLocation(device.getBindingLocation())
                .locationType(device.getLocationType())
                .physicalLocationType(device.getPhysicalLocationType())
                .deviceDescription(device.getDeviceDescription())
                .dataValue(String.valueOf(val.getValue()))
                .build();
        })
        .collect(Collectors.toList()));*/

    }

    @Override
    public PageResponse<DeviceDataVo> getDeviceDataPage(DeviceDataPageReqDto dto) {
        PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
        //前端传递的时间是一个long类型的毫秒值，作为条件查询数据库需要转换为日期类型
        Long startTime = dto.getStartTime();
        Long endTime = dto.getEndTime();

        dto.setStartDate(ObjectUtil.isEmpty(startTime) ? null : LocalDateTimeUtil.of(startTime));
        dto.setEndDate(ObjectUtil.isEmpty(endTime) ? null : LocalDateTimeUtil.of(endTime));
        //查询
        Page<DeviceDataVo> page = deviceDataMapper.selectByPage(dto);

        return PageResponse.of(page, DeviceDataVo.class);
    }

    @Override
    public QueryDevicePropertyStatusResponseBody.QueryDevicePropertyStatusResponseBodyData queryDevicePropertyStatus(DeviceDto dto) {
        // 调用IOT和api
        // 创建查询设备属性状态的请求对象
        QueryDevicePropertyStatusRequest request = new QueryDevicePropertyStatusRequest();

        // 设置请求的实例ID，该ID用于标识特定的物联网平台实例
        request.setIotInstanceId(iotProp.getIotInstanceId());

        // 设置请求的设备名称，用于标识要查询的设备
        request.setDeviceName(dto.getDeviceName());

        // 设置请求的产品Key，用于标识设备所属的产品
        request.setProductKey(dto.getProductKey());

        QueryDevicePropertyStatusResponse response = null;

        // 尝试通过客户端发送请求，查询设备属性状态
        try {
            response = client.queryDevicePropertyStatus(request);
        } catch (Exception e) {
            // 如果查询过程中出现异常，打印异常信息
            e.printStackTrace();
        }

        // 检查查询响应是否为空或查询是否成功，如果失败则记录错误日志并抛出异常
        // todo 有疑问明天解决
        if (response == null || !response.getBody().success) {
            log.error("查询设备状态失败");
            throw new BaseException(BasicEnum.IOT_QUERY_DEVICE_ERROR);
        }

        // 从响应中提取查询结果数据
        QueryDevicePropertyStatusResponseBody.QueryDevicePropertyStatusResponseBodyData data = response.getBody().getData();

        // 返回查询结果数据
        return data;

    }

    @Override
    public List<DeviceDataGraphVo> queryDeviceDataListByDay(String iotId, String functionId, Long startTime, Long endTime) {
        //  把时间戳转换为LocalDateTime 类型 begin 00:00 end 23:00
        // 将startTime毫秒数转换为本地日期时间，使用系统默认时区
        LocalDateTime start = LocalDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneId.systemDefault());
        // 通过Util方法将endTime转换为本地日期时间，该方法的具体实现未显示
        LocalDateTime end = LocalDateTimeUtil.of(endTime);
        // 根据iotId, functionId, startTime, endTime查询设备数据列表
        List<DeviceDataGraphVo> voList = deviceDataMapper.queryDeviceDataListByDay(iotId, functionId, start, end);
        // 初始化一个空列表，用于存储最终的设备数据图形化视图对象
        List<DeviceDataGraphVo> result = new ArrayList<>();
// 第一种写法
/*        // 遍历24个小时，分别处理每个小时的数据
        for (int i = 0; i < 24; i++) {
            // 默认标记为false，表示在voList中未找到匹配的DeviceDataGraphVo对象
            boolean b = false;
            // 初始化vo为null，用于存储当前小时对应的数据图形化视图对象
            DeviceDataGraphVo vo = null;

            // 遍历voList，寻找与当前小时start匹配的DeviceDataGraphVo对象
            for (DeviceDataGraphVo graphVo : voList) {
                // 如果当前遍历到的graphVo的日期时间不等于start，则找到匹配项
                if (!start.equals(graphVo.getDateTime())) {
                    // 标记找到匹配项
                    b = true;
                    // 将匹配到的graphVo赋值给vo
                    vo = graphVo;
                    // 结束当前循环，进入下个小时的处理
                    break;
                }
            }

            // 如果没有找到匹配的DeviceDataGraphVo对象（即b为false）
            if (!b) {
                // 创建一个新的DeviceDataGraphVo对象，用于当前小时的数据
                // 如果时间不在数据库中就自己创建
                vo = new DeviceDataGraphVo();
                // 设置当前小时的时间
                vo.setDateTime(start.format(DateTimeFormatter.ofPattern("HH:00")));
                // 初始化数据值为0.0
                vo.setDataValue(0.0);
            }

            // 将当前小时的数据图形化视图对象添加到结果列表中
            result.add(vo);
            // 更新start为下一个小时，为下一次循环做准备
            start = start.plusHours(1);
        }
        // 返回处理后的24个小时的数据图形化视图对象列表*/
        // 第二种写法
        // 把数据库返回的集合进行处理, 处理成Map结构Map<dateTime, DeviceDataGraphVo>使用流结构
        // 使用流对voList进行处理，根据日期时间字段将DeviceDataGraphVo对象映射到一个Map中
        // Key为日期时间，Value为对应的DeviceDataGraphVo对象
        Map<String, DeviceDataGraphVo> map = voList.stream().collect(Collectors.toMap(DeviceDataGraphVo::getDateTime, Function.identity()));

        // 遍历24个小时，为每个小时生成一个DeviceDataGraphVo对象，并添加到结果列表中
        for (int i = 0; i < 24; i++) {
            // 根据当前小时生成起始时间字符串，格式为"HH:00"
            String begin = start.format(DateTimeFormatter.ofPattern("HH:00"));
            // 从map中获取对应小时的DeviceDataGraphVo对象，如果不存在，则创建一个新的对象
            // 这里的orDefault用于处理map中不存在key时的情况，会返回一个默认的对象
            DeviceDataGraphVo orDefault = map.getOrDefault(begin, new DeviceDataGraphVo(begin, 0.0));
            // 将当前小时的DeviceDataGraphVo对象添加到结果列表中
            result.add(orDefault);
            // 将开始时间增加1小时，为下一个小时的处理做准备
            start = start.plusHours(1);
        }

        return result;

    }

    @Override
    public List<DeviceDataGraphVo> queryDeviceDataListByWeek(String iotId, String functionId, Long startTime, Long endTime) {
        List<DeviceDataGraphVo> result = new ArrayList<>();
        //  把时间戳转换为LocalDateTime 类型 begin 2024-06-09 00:00 end 2024-06-14 23:00
        // 将startTime毫秒数转换为本地日期时间，使用系统默认时区
        LocalDateTime begin = LocalDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneId.systemDefault());
        // 通过Util方法将endTime转换为本地日期时间，该方法的具体实现未显示
        LocalDateTime end = LocalDateTimeUtil.of(endTime);
        if (begin.isAfter(end)) {
            // 如果开始时间晚于结束时间，则抛出异常
            throw new BaseException(BasicEnum.IOT_QUERY_DEVICE_ERROR);
        }
        List<DeviceDataGraphVo> voList = deviceDataMapper.queryDeviceDataListByWeek(iotId, functionId, begin, end);
        Map<String, DeviceDataGraphVo> map = voList.stream().collect(Collectors.toMap(DeviceDataGraphVo::getDateTime, Function.identity()));
        for (int i = 0; i < 7; i++) {
            String beginStr = begin.format(DateTimeFormatter.ofPattern("MM.dd"));
            DeviceDataGraphVo orDefault = map.getOrDefault(beginStr, new DeviceDataGraphVo(beginStr, 0.0));
            result.add(orDefault);
            begin = begin.plusDays(1);
        }

        return result;
    }

    @Override
    public QueryThingModelPublishedResponseBody.QueryThingModelPublishedResponseBodyData QueryThingModelPublished(DeviceDto dto) {
        QueryThingModelPublishedRequest request = new QueryThingModelPublishedRequest()
                .setIotInstanceId(iotProp.getIotInstanceId())
                .setProductKey(dto.getProductKey());
        QueryThingModelPublishedResponse response =null;
        try {
         response=   client.queryThingModelPublished(request);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("查询产品模型失败");
            throw new BaseException(BasicEnum.IOT_QUERY_THING_MODEL_PUBLISHED_ERROR);
        }
        if (!response.getBody().getSuccess()||response==null){
            log.error("查询产品模型失败");
            throw new BaseException(BasicEnum.IOT_QUERY_THING_MODEL_PUBLISHED_ERROR);
        }

        return response.getBody().getData();
    }

}
